/* logsqlite3_daemon - This is a derivitive work of logmysqldb_daemon.c, whose 
 * copyright message has been left intact below.
 * Released under GPL V2
 */

/* logmysqldb_daemon.c - This file reads the access info from squid on stdin and logs to mysql database
 * (C) 2008 ViSolve Inc..
 * Released under GPL V2 
 */

/* Sending info to stdout would increase squid buffers so supress them all other 
 * than for trouble-shooting by "undef LDM_DEBUG".
 */

#include "logging_daemon.h"

void
foo(void) {
    printf("foo\n");
}

static void
logto_db(sqlite3 *db, sqlite3_stmt *ppStmt, char *lbuf) {
    int err[14];
    char *fld[15];
    int fc=0;
    int i;
    
    fld[0]=strtok(lbuf, " ");
    while (fld[fc] != NULL && fc < COLUMN_COUNT) {
        fc++;
        fld[fc]=strtok(NULL, " ");
    }
    if (fc > 0) {
        fld[fc-1]=strtok(fld[fc-1], "\r\n");
        if ( fld[fc-1] == NULL ) {
            fc--;  
        }
    }
    if ( fc == COLUMN_COUNT ) {
        err[0] = sqlite3_reset(ppStmt);
        err[1] = sqlite3_bind_double(ppStmt, 1, atof(fld[0]));
        err[2] = sqlite3_bind_int(ppStmt, 2, atoi(fld[1]));
        err[3] = sqlite3_bind_text(ppStmt, 3, fld[2], strlen(fld[2]), SQLITE_STATIC);
        err[4] = sqlite3_bind_text(ppStmt, 4, fld[3], strlen(fld[3]), SQLITE_STATIC);
        err[5] = sqlite3_bind_text(ppStmt, 5, fld[4], strlen(fld[4]), SQLITE_STATIC);
        err[6] = sqlite3_bind_int(ppStmt, 6, atoi(fld[5]));
        err[7] = sqlite3_bind_text(ppStmt, 7, fld[6], strlen(fld[6]), SQLITE_STATIC);
        err[8] = sqlite3_bind_text(ppStmt, 8, fld[7], strlen(fld[7]), SQLITE_STATIC);
        err[9] = sqlite3_bind_text(ppStmt, 9, fld[8], strlen(fld[8]), SQLITE_STATIC);
        err[10] = sqlite3_bind_text(ppStmt, 10, fld[9], strlen(fld[9]), SQLITE_STATIC);
        err[11] = sqlite3_bind_text(ppStmt, 11, fld[10], strlen(fld[10]), SQLITE_STATIC);
        err[12] = sqlite3_bind_text(ppStmt, 12, fld[11], strlen(fld[11]), SQLITE_STATIC);
        err[13] = sqlite3_step(ppStmt);
        for (i = 0; i <= NUM_BINDS + 1; ++i) {
            if (err[i] != SQLITE_OK && err[i] != SQLITE_DONE) { 
                syslog(LOG_CRIT, "Error on binding %d: %d %s\n", i, err[i], sqlite3_errmsg(db));
            }
        }
    }
}


void
begin_transaction(sqlite3 *db) {
    char *buffer;
    int rc;

    syslog(LOG_INFO, "Beginning transaction...");
    rc = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &buffer);
    if (rc) {
        syslog(LOG_CRIT, "Can't begin transaction: %s %s\n", sqlite3_errmsg(db), buffer);
        sqlite3_free(buffer);
        sqlite3_close(db);
        exit(1);
    }
    syslog(LOG_INFO, "Begun transaction");
}


/* This returns zero, if successful and one if it fails to rollback. */
int
rollback(sqlite3 *db) {
    char *buffer;
    int rc;

    syslog(LOG_INFO, "Rolling back transaction...");
    rc = sqlite3_exec(db, "ROLLBACK", NULL, NULL, &buffer);
    if (rc) {
        syslog(LOG_CRIT, "Can't roll back transaction: %s %s\n", sqlite3_errmsg(db), buffer);
        sqlite3_free(buffer);
        //sqlite3_close(db);
        //exit(1);
        return 1;
    }
    syslog(LOG_INFO, "Rolled back transaction.");
    return 0;
}


/* This returns zero, if successful and one if it fails to commit. */
int
commit_transaction(sqlite3 *db) {
    char *buffer;
    int rc;

    syslog(LOG_INFO, "Committing transaction...");
    rc = sqlite3_exec(db, "COMMIT", NULL, NULL, &buffer);
    if (rc) {
        syslog(LOG_CRIT, "Can't commit transaction: %s %s\n", sqlite3_errmsg(db), buffer);
        sqlite3_free(buffer);
        //sqlite3_close(db);
        //exit(1);
        return 1;
    }
    syslog(LOG_INFO, "Committed transaction.");
    return 0;
}
    

void
prepare_db(sqlite3 **pdb, sqlite3_stmt **ppStmt, char *db_file) {
    int rc;
    const char query[LOGFILE_BUF_LEN + 512];

    syslog(LOG_INFO, "Preparing database...");
    rc = sqlite3_open(db_file, pdb);
    if (rc) {
        syslog(LOG_CRIT, "Can't open database: %s\n", sqlite3_errmsg(*pdb));
        sqlite3_close(*pdb);
        exit(1);
    }

    memset(query, 0, strlen (query) + 1);
    sprintf(query, "INSERT INTO %s VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ACCESS_LOG);
    rc = sqlite3_prepare_v2(*pdb, query, LOGFILE_BUF_LEN + 512, ppStmt, NULL);
    if (rc) {
        syslog(LOG_CRIT, "Can't prepare statement: %s\n", sqlite3_errmsg(*pdb));
        sqlite3_close(*pdb);
        exit(1);
    }

    syslog(LOG_INFO, "Prepared database.");
}


// TODO: in production code this should also be called in response to SIGTERM, etc.
void
teardown_db(sqlite3 *db) {
    syslog(LOG_INFO, "Tearing down database...");
    sqlite3_close(db);
    syslog(LOG_INFO, "Torn down database.");
}


/*
 * The commands:
 *
 * L<data>\n - logfile data
 * R\n - rotate file
 * T\n - truncate file
 * O\n - repoen file
 * F\n - flush file
 * r<n>\n - set rotate count to <n>
 * b<n>\n - 1 = buffer output, 0 = don't buffer output
 */

int
logging_daemon(const char *db_filename) {
    int t;
    int i;

    char buf[LOGFILE_BUF_LEN];
    char* store[MAX_LINES];
    char* store_buffer;
    int current_line = 0;

    sqlite3 *db;
    sqlite3_stmt *ppStmt;

    int need_to_flush = 0; // set to one, if a flush is required

    syslog (LOG_INFO, "starting...");

    /* get some room for the store */
    store_buffer = (char *) malloc(MAX_LINES * LINE_LEN); 
    for (i = 0; i < MAX_LINES; i++) {
        store[i] = store_buffer + i * LINE_LEN;
    }

    setbuf(stdout, NULL);
    close(2);
    t = open(_PATH_DEVNULL, O_RDWR);
    assert(t > -1);
    dup2(t, 2);
    /* supress the o/p incase */
    close(1);
    dup2(t, 1);

    prepare_db(&db, &ppStmt, db_filename);

    while (fgets(buf, LOGFILE_BUF_LEN, stdin)) {
        /* first byte is the logging command */
        switch (buf[0]) {
        case 'L':
            if (buf[1] != '\0') {
                if (current_line >= MAX_LINES) {
                    syslog (LOG_WARNING, "Cannot log more than %i lines between flushes", MAX_LINES);
                } else {
                    strncpy(store[current_line], buf+1, LINE_LEN);
                    /* terminate with a zero, just in case line is longer than LINE_LEN */
                    store[current_line][LINE_LEN - 1] = '\0';  
                    current_line++;
                }
            }
            break;
        case 'R':
            syslog (LOG_INFO, "Squid log-rotating, doing nothing");
            break;
        case 'T':
            break;
        case 'O':
            break;
        case 'r':
            break;
        case 'b':
            break;
        case 'F':
            syslog (LOG_DEBUG, "Squid flush");
            need_to_flush = 1;
            break;
        default:
            break;
        }
        if (current_line > MAX_LINES / 2) {
            need_to_flush = 1;
        }
        if (need_to_flush) {
            syslog (LOG_DEBUG, "Squid flush - committing %i lines to db", current_line);
            begin_transaction(db);
            for (i = 0; i < current_line; i++) {
                logto_db(db, ppStmt, store[i]);
            }
            if (commit_transaction(db)) {
                // the commit has failed, need to rollback
                syslog (LOG_WARNING, "Squid flush - failed to commit %i lines to db", current_line);
                rollback(db);
            } else {
                syslog (LOG_DEBUG, "Squid flush - committed %i lines to db", current_line);
                // reset counter
                current_line = 0;
                need_to_flush = 0;
            }
        }
    }
    teardown_db(db);
    exit(0);
}
